// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include "content/browser/renderer_host/input/synthetic_touchpad_pinch_gesture.h"

namespace content {
namespace {

    float Lerp(float start, float end, float progress)
    {
        return start + progress * (end - start);
    }

} // namespace

SyntheticTouchpadPinchGesture::SyntheticTouchpadPinchGesture(
    const SyntheticPinchGestureParams& params)
    : params_(params)
    , gesture_source_type_(SyntheticGestureParams::DEFAULT_INPUT)
    , state_(SETUP)
    , current_scale_(1.0f)
{
    DCHECK_GT(params_.scale_factor, 0.0f);
}

SyntheticTouchpadPinchGesture::~SyntheticTouchpadPinchGesture() { }

SyntheticGesture::Result SyntheticTouchpadPinchGesture::ForwardInputEvents(
    const base::TimeTicks& timestamp,
    SyntheticGestureTarget* target)
{
    if (state_ == SETUP) {
        gesture_source_type_ = params_.gesture_source_type;
        if (gesture_source_type_ == SyntheticGestureParams::DEFAULT_INPUT)
            gesture_source_type_ = target->GetDefaultSyntheticGestureSourceType();

        state_ = STARTED;
        start_time_ = timestamp;
    }

    DCHECK_NE(gesture_source_type_, SyntheticGestureParams::DEFAULT_INPUT);
    if (gesture_source_type_ == SyntheticGestureParams::MOUSE_INPUT) {
        ForwardGestureEvents(timestamp, target);
    } else {
        // Touch input should be using SyntheticTouchscreenPinchGesture.
        return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED;
    }

    return (state_ == DONE) ? SyntheticGesture::GESTURE_FINISHED
                            : SyntheticGesture::GESTURE_RUNNING;
}

void SyntheticTouchpadPinchGesture::ForwardGestureEvents(
    const base::TimeTicks& timestamp,
    SyntheticGestureTarget* target)
{
    switch (state_) {
    case STARTED:
        // Check for an early finish.
        if (params_.scale_factor == 1.0f) {
            state_ = DONE;
            break;
        }

        CalculateEndTime(target);

        // Send the start event.
        target->DispatchInputEventToPlatform(
            SyntheticWebGestureEventBuilder::Build(
                blink::WebGestureEvent::GesturePinchBegin,
                blink::WebGestureDeviceTouchpad));
        state_ = IN_PROGRESS;
        break;
    case IN_PROGRESS: {
        base::TimeTicks event_timestamp = ClampTimestamp(timestamp);

        float target_scale = CalculateTargetScale(event_timestamp);
        float incremental_scale = target_scale / current_scale_;
        current_scale_ = target_scale;

        // Send the incremental scale event.
        target->DispatchInputEventToPlatform(
            SyntheticWebGestureEventBuilder::BuildPinchUpdate(
                incremental_scale, params_.anchor.x(), params_.anchor.y(),
                0 /* modifierFlags */, blink::WebGestureDeviceTouchpad));

        if (HasReachedTarget(event_timestamp)) {
            target->DispatchInputEventToPlatform(
                SyntheticWebGestureEventBuilder::Build(
                    blink::WebGestureEvent::GesturePinchEnd,
                    blink::WebGestureDeviceTouchpad));
            state_ = DONE;
        }
        break;
    }
    case SETUP:
        NOTREACHED() << "State SETUP invalid for synthetic pinch.";
    case DONE:
        NOTREACHED() << "State DONE invalid for synthetic pinch.";
    }
}

float SyntheticTouchpadPinchGesture::CalculateTargetScale(
    const base::TimeTicks& timestamp) const
{
    // Make sure the final delta is correct. Using the computation below can lead
    // to issues with floating point precision.
    if (HasReachedTarget(timestamp))
        return params_.scale_factor;

    float progress = (timestamp - start_time_).InSecondsF() / (stop_time_ - start_time_).InSecondsF();
    return Lerp(1.0f, params_.scale_factor, progress);
}

// Calculate an end time based on the amount of scaling to be done and the
// |relative_pointer_speed_in_pixels_s|. Because we don't have an actual pixel
// delta, we assume that a pinch of 200 pixels is needed to double the screen
// size and generate a stop time based on that.
// TODO(ericrk): We should not calculate duration from
// |relative_pointer_speed_in_pixels_s|, but should instead get a duration from
// a SyntheticTouchpadPinchGestureParams type. crbug.com/534976
void SyntheticTouchpadPinchGesture::CalculateEndTime(
    SyntheticGestureTarget* target)
{
    const int kPixelsNeededToDoubleOrHalve = 200;

    float scale_factor = params_.scale_factor;
    if (scale_factor < 1.0f) {
        // If we are scaling down, calculate the time based on the inverse so that
        // halving or doubling the scale takes the same amount of time.
        scale_factor = 1.0f / scale_factor;
    }
    float scale_factor_delta = (scale_factor - 1.0f) * kPixelsNeededToDoubleOrHalve;

    int64_t total_duration_in_us = static_cast<int64_t>(1e6 * (static_cast<double>(scale_factor_delta) / params_.relative_pointer_speed_in_pixels_s));
    DCHECK_GT(total_duration_in_us, 0);
    stop_time_ = start_time_ + base::TimeDelta::FromMicroseconds(total_duration_in_us);
}

base::TimeTicks SyntheticTouchpadPinchGesture::ClampTimestamp(
    const base::TimeTicks& timestamp) const
{
    return std::min(timestamp, stop_time_);
}

bool SyntheticTouchpadPinchGesture::HasReachedTarget(
    const base::TimeTicks& timestamp) const
{
    return timestamp >= stop_time_;
}

} // namespace content
